{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# XXX on Amazon SageMaker\n",
    "\n",
    "XXX on Amazon SageMaker\n",
    "\n",
    "## Contents\n",
    "\n",
    "\n",
    "1. [Background](#Background)\n",
    "1. [Setup](#Setup)\n",
    "1. [Model Preparation](#Model-Preparation)\n",
    "1. [SageMaker Model Hosting (BYOM)](#Hosting-Model-BYOM)\n",
    "1. [SageMaker Model Hosting (BYOC)](#Hosting-Model-BYOC)\n",
    "1. [Build a KNN Index in Elasticsearch](#ES-KNN)\n",
    "1. [Evaluate Index Search Results](#Searching-with-ES-k-NN)\n",
    "1. [Extensions](#Extensions)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Background\n",
    "\n",
    "In this notebook, we'll ..."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Setup"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO ONLY for China region\n",
    "# !pip config set global.index-url https://opentuna.cn/pypi/web/simple/\n",
    "    \n",
    "# Install tqdm to have progress bar\n",
    "!pip install tqdm\n",
    "\n",
    "# Install necessary pkg to make connection with elasticsearch domain\n",
    "!pip install elasticsearch==7.13.0\n",
    "!pip install requests\n",
    "!pip install requests-aws4auth\n",
    "\n",
    "# Install gluoncv\n",
    "!pip install gluoncv"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import boto3\n",
    "import re\n",
    "import sagemaker\n",
    "from sagemaker import get_execution_role\n",
    "\n",
    "role = get_execution_role()\n",
    "\n",
    "s3_resource = boto3.resource(\"s3\")\n",
    "s3 = boto3.client('s3')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cfn = boto3.client('cloudformation')\n",
    "\n",
    "def get_cfn_outputs(stackname):\n",
    "    outputs = {}\n",
    "    for output in cfn.describe_stacks(StackName=stackname)['Stacks'][0]['Outputs']:\n",
    "        outputs[output['OutputKey']] = output['OutputValue']\n",
    "    return outputs\n",
    "\n",
    "## TODO Setup variables to use for the rest of the demo\n",
    "cloudformation_stack_name = \"xxx-sagemaker\"\n",
    "\n",
    "outputs = get_cfn_outputs(cloudformation_stack_name)\n",
    "\n",
    "bucket = outputs['s3BucketTraining']\n",
    "es_host = outputs['esHostName']\n",
    "\n",
    "outputs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "!wget -O images.zip \"https://datalab.s3.amazonaws.com/data/images.zip?AWSAccessKeyId=AKIAYNUCDPLSDWHHQJ7Y&Signature=46e9JV6g8onErubg0lQ04U1I1TA%3D&Expires=1637339146\"\n",
    "!unzip -q images.zip\n",
    "!aws s3 cp --recursive images s3://$bucket/images"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Model-Preparation\n",
    "\n",
    "We'll use GluonCV to inference. You can use [train.ipynb](train.ipynb) to finetune your model on your own dataset, or you can download the finetuned model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!wget -O docker/model.zip \"https://datalab.s3.amazonaws.com/data/model.zip?AWSAccessKeyId=AKIAYNUCDPLSDWHHQJ7Y&Signature=q7WWg99ntv1ZwJoldg9Dl9D0C3k%3D&Expires=1637341690\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!cd docker && unzip -q model.zip"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import mxnet as mx\n",
    "from mxnet.gluon import nn\n",
    "from mxnet import gluon, image, init, nd\n",
    "from gluoncv.model_zoo import get_model\n",
    "from gluoncv.data.transforms.presets.imagenet import transform_eval"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "num_gpus = 0\n",
    "ctx = [mx.gpu(i) for i in range(num_gpus)] if num_gpus > 0 else [mx.cpu()]\n",
    "\n",
    "model_name = 'ResNet50_v2'\n",
    "classes = 5\n",
    "\n",
    "finetune_net = get_model(model_name, pretrained=True)\n",
    "with finetune_net.name_scope():\n",
    "    finetune_net.output = nn.Dense(classes)\n",
    "finetune_net.output.initialize(init.Xavier(), ctx = ctx)\n",
    "finetune_net.collect_params().reset_ctx(ctx)\n",
    "finetune_net.hybridize()\n",
    "\n",
    "finetune_net.load_parameters('docker/model/model-0000.params')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "input_pic = 'docker/1.jpg'\n",
    "\n",
    "# Load Images\n",
    "img = image.imread(input_pic)\n",
    "\n",
    "# Transform\n",
    "img = transform_eval(img).copyto(ctx[0])\n",
    "    \n",
    "finetune_net(img)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Hosting-Model-BYOM\n",
    "\n",
    "TBD\n",
    "\n",
    "After saving the feature extractor model we will deploy the model using Sagemaker Tensorflow Serving api which is a flexible, high-performance serving system for machine learning models, designed for production environments.TensorFlow Serving makes it easy to deploy new algorithms and experiments, while keeping the same server architecture and APIs. TensorFlow Serving provides out-of-the-box integration with TensorFlow models, but can be easily extended to serve other types of models and data. We will define **inference.py** to customize the input data to TensorFlow serving API. We also need to add **requirements.txt** file for aditional libraby in the tensorflow serving container."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# #check the actual content of inference.py\n",
    "# !pygmentize docker/inference.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# import tarfile\n",
    "\n",
    "# #zip the model .gz format\n",
    "# export_dir = ''\n",
    "# with tarfile.open('model.tar.gz', mode='w:gz') as archive:\n",
    "#     archive.add('docker/model', recursive=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# #Upload the model to S3\n",
    "# sagemaker_session = sagemaker.Session()\n",
    "# inputs = sagemaker_session.upload_data(path='model.tar.gz', key_prefix='model')\n",
    "# inputs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "After we upload the model to S3 we will use TensorFlow serving container to host the model. We are using ml.p3.16xlarge instance type. You may need to raise support ticket to increase the Service quotas for SageMaker hosting instance type. We will use this endpoint to generate features and import into ElasticSearch. you can also choose small instance such as \"ml.m4.xlarge\" to save cost."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# #Deploy the model in Sagemaker Endpoint. This process will take ~10 min.\n",
    "# from sagemaker.mxnet.serving import Model\n",
    "\n",
    "# sagemaker_model = Model(entry_point='inference.py', model_data = 's3://' + sagemaker_session.default_bucket() + '/model/model.tar.gz',\n",
    "#                                   role = role, framework_version='1.6.0', source_dir='./src' )\n",
    "\n",
    "# predictor = sagemaker_model.deploy(initial_instance_count=3, instance_type='ml.m5.xlarge')\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# # get the features for a sample image\n",
    "# payload = s3.get_object(Bucket=bucket,Key='image-embedding/1.jpg')['Body'].read()\n",
    "# predictor.content_type = 'application/x-image'\n",
    "# predictor.serializer   = None\n",
    "# features = predictor.predict(payload)['predictions'][0]\n",
    "\n",
    "# features"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Hosting-Model-BYOC\n",
    "```\n",
    "cd docker\n",
    "./build_and_push.sh xxx-sagemaker\n",
    "python create_endpoint.py\n",
    "docker run -v -d -p 8080:8080 xxx-sagemaker\n",
    "python test.py\n",
    "python test-x-image.py\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## ES-KNN\n",
    "\n",
    "KNN for Amazon Elasticsearch Service lets you search for points in a vector space and find the \"nearest neighbors\" for those points by Euclidean distance or cosine similarity (default is Euclidean distance). Use cases include recommendations (for example, an \"other songs you might like\" feature in a music application), image recognition, and fraud detection.\n",
    "\n",
    "KNN requires Elasticsearch 7.1 or later. Full documentation for the Elasticsearch feature, including descriptions of settings and statistics, is available in the Open Distro for Elasticsearch documentation. For background information about the k-nearest neighbors algorithm\n",
    "\n",
    "In this step we'll get all the features zalando images and import those features into Elastichseach7.4 domain."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Define some utility function\n",
    "\n",
    "#return all s3 keys\n",
    "def get_all_s3_keys(bucket):\n",
    "    \"\"\"Get a list of all keys in an S3 bucket.\"\"\"    \n",
    "    keys = []\n",
    "\n",
    "    kwargs = {'Bucket': bucket}\n",
    "    while True:\n",
    "        resp = s3.list_objects_v2(**kwargs)\n",
    "        for obj in resp['Contents']:\n",
    "            if obj['Key'].endswith('.jpg'):\n",
    "                keys.append('s3://' + bucket + '/' + obj['Key'])\n",
    "\n",
    "        try:\n",
    "            kwargs['ContinuationToken'] = resp['NextContinuationToken']\n",
    "        except KeyError:\n",
    "            break\n",
    "\n",
    "    return keys"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# get all the zalando images keys from the bucket make a list\n",
    "s3_uris = get_all_s3_keys(bucket)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "len(s3_uris)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "s3_uris[:10]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# define a function to extract image features\n",
    "from time import sleep\n",
    "import json\n",
    "\n",
    "sm_client = boto3.client('sagemaker-runtime')\n",
    "ENDPOINT_NAME = 'xxx-sagemaker'  # TODO sagemaker endpoint name\n",
    "\n",
    "def get_predictions(payload):\n",
    "    return sm_client.invoke_endpoint(EndpointName=ENDPOINT_NAME,\n",
    "                                           ContentType='application/json',  # 'application/x-image'\n",
    "                                           Body=payload)\n",
    "\n",
    "def extract_features(s3_uri):\n",
    "    key = s3_uri.replace(f's3://{bucket}/', '')\n",
    "    payload = json.dumps({'bucket' : bucket, 'image_uri' : key, 'content_type': \"application/json\"})  # s3.get_object(Bucket=bucket,Key=key)['Body'].read()\n",
    "    try:\n",
    "        response = get_predictions(payload)\n",
    "    except:\n",
    "        sleep(0.1)\n",
    "        response = get_predictions(payload)\n",
    "\n",
    "    del payload\n",
    "    response_body = json.loads((response['Body'].read()))\n",
    "    feature_lst = response_body  # response_body['predictions'][0]\n",
    "    \n",
    "    return s3_uri, feature_lst\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "resulti = extract_features(s3_uris[0])\n",
    "print(resulti)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "# This process cell will take approximately 24-25 minutes on a t3.medium notebook instance\n",
    "# with 3 m5.xlarge SageMaker Hosted Endpoint instances\n",
    "from multiprocessing import cpu_count\n",
    "from tqdm.contrib.concurrent import process_map\n",
    "\n",
    "workers = 1  # TODO if your endpoint instance is large enough: 2 * cpu_count()\n",
    "result = process_map(extract_features, s3_uris, max_workers=workers)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# setting up the Elasticsearch connection\n",
    "from elasticsearch import Elasticsearch, RequestsHttpConnection\n",
    "from requests_aws4auth import AWS4Auth\n",
    "my_session = boto3.session.Session()\n",
    "region = my_session.region_name\n",
    "service = 'es'\n",
    "credentials = boto3.Session().get_credentials()\n",
    "awsauth = AWS4Auth(credentials.access_key, credentials.secret_key, region, service, session_token=credentials.token)\n",
    "\n",
    "es = Elasticsearch(\n",
    "    hosts = [{'host': es_host, 'port': 443}],\n",
    "    http_auth = awsauth,\n",
    "    use_ssl = True,\n",
    "    verify_certs = True,\n",
    "    connection_class = RequestsHttpConnection\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Define KNN Elasticsearch index maping\n",
    "knn_index = {\n",
    "    \"settings\": {\n",
    "        \"index.knn\": True\n",
    "    },\n",
    "    \"mappings\": {\n",
    "        \"properties\": {\n",
    "            \"img_vector\": {\n",
    "                \"type\": \"knn_vector\",\n",
    "                \"dimension\": 2048\n",
    "            }\n",
    "        }\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Creating the Elasticsearch index\n",
    "es.indices.create(index=\"idx\",body=knn_index,ignore=400)\n",
    "es.indices.get(index=\"idx\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "%%time\n",
    "# defining a function to import the feature vectors corrosponds to each S3 URI into Elasticsearch KNN index\n",
    "# This process will take around ~3 min.\n",
    "\n",
    "\n",
    "def es_import(i):\n",
    "    es.index(index='idx',\n",
    "             body={\"img_vector\": i[1]['predictions'][0], \n",
    "                   \"image\": i[0]}\n",
    "            )\n",
    "    \n",
    "_ = process_map(es_import, result, max_workers=workers)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Searching-with-ES-k-NN\n",
    "\n",
    "In this step we will use SageMaker SDK as well as Boto3 SDK to query the Elasticsearch to retrive the nearest neighbours. One thing to mention **zalando** dataset has pretty good similarity with Imagenet dataset. Now if you hav a very domain speific problem then then you need to train that dataset on top of pretrained feature extractor model such as VGG, Resnet, Xeception, Mobilenet etc and bulid a new feature extractor model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#define display_image function\n",
    "def display_image(bucket, key):\n",
    "    response = s3.get_object(Bucket=bucket,Key=key)['Body']\n",
    "    img = Image.open(response)\n",
    "    return display(img)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import requests\n",
    "import random\n",
    "from PIL import Image\n",
    "import io"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###### SageMaker SDK Method"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Boto3 approach\n",
    "tmp_bucket = bucket\n",
    "s3_uri = f's3://{bucket}/images/1,12,0,4,22432,3005,2000,8a28288b.jpg'\n",
    "key = s3_uri.replace(f's3://{tmp_bucket}/', '')\n",
    "payload = json.dumps({'bucket' : tmp_bucket, 'image_uri' : key, 'content_type': \"application/json\"})  # s3.get_object(Bucket=bucket,Key=key)['Body'].read()\n",
    "response = get_predictions(payload)\n",
    "response_body = json.loads((response['Body'].read()))\n",
    "features = response_body['predictions'][0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import json\n",
    "k = 5\n",
    "idx_name = 'idx'\n",
    "res = es.search(request_timeout=30, index=idx_name,\n",
    "                body={'size': k, \n",
    "                      'query': {'knn': {'img_vector': {'vector': features, 'k': k}}}})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for i in range(k):\n",
    "    key = res['hits']['hits'][i]['_source']['image']\n",
    "    key = key.replace(f's3://{bucket}/','')\n",
    "    if key.startswith('s3'):\n",
    "        key = 'images'+key\n",
    "    print(key)\n",
    "    img = display_image(bucket,key)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Deploying a full-stack visual search application"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "!cd backend/lambda && pip install -r requirements.txt --target ./ && zip -r ../function.zip ./"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "s3_resource.Object(bucket, 'backend/function.zip').upload_file('./backend/function.zip', ExtraArgs={'ACL':'public-read'})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!sed -i 's/S3_BUCKET_NAME/{bucket}/' backend/template_cn.yaml\n",
    "!sed -i 's/S3_BUCKET_NAME/{bucket}/' backend/template.yaml"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if region.startswith('cn-'):\n",
    "    s3_resource.Object(bucket, 'backend/template_cn.yaml').upload_file('./backend/template_cn.yaml', ExtraArgs={'ACL':'public-read'})\n",
    "    sam_template_url = f'https://{bucket}.s3.{region}.amazonaws.com.cn/backend/template_cn.yaml'\n",
    "    console_url = 'https://console.amazonaws.cn'\n",
    "else:\n",
    "    s3_resource.Object(bucket, 'backend/template.yaml').upload_file('./backend/template.yaml', ExtraArgs={'ACL':'public-read'})\n",
    "    sam_template_url = f'https://{bucket}.s3.amazonaws.com/backend/template.yaml'\n",
    "    console_url = 'https://console.aws.amazon.com'\n",
    "\n",
    "# Generate the CloudFormation Quick Create Link\n",
    "\n",
    "print(\"Click the URL below to create the backend API for visual search:\\n\")\n",
    "print((\n",
    "    console_url+'/cloudformation/home?region='+region+'#/stacks/create/review'\n",
    "    f'?templateURL={sam_template_url}'\n",
    "    '&stackName=xxx-sagemaker-api'\n",
    "    f'&param_BucketName={outputs[\"s3BucketTraining\"]}'\n",
    "    f'&param_DomainName={outputs[\"esDomainName\"]}'\n",
    "    f'&param_ElasticSearchURL={outputs[\"esHostName\"]}'\n",
    "    f'&param_SagemakerEndpoint=xxx-sagemaker'  # TODO endpoint name\n",
    "))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now that you have a working Amazon SageMaker endpoint for extracting image features and a KNN index on Elasticsearch, you are ready to build a real-world full-stack ML-powered web app. The SAM template you just created will deploy an Amazon API Gateway and AWS Lambda function. The Lambda function runs your code in response to HTTP requests that are sent to the API Gateway."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Review the content of the Lambda function code.\n",
    "!pygmentize backend/lambda/app.py"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Once the CloudFormation Stack shows CREATE_COMPLETE, proceed to this cell below:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Save the REST endpoint for the search API to a config file, to be used by the frontend build\n",
    "\n",
    "import json\n",
    "api_endpoint = get_cfn_outputs('xxx-sagemaker-api')['ImageSimilarityApi']  # TODO cloudformation name\n",
    "\n",
    "with open('./frontend/src/config/config.json', 'w') as outfile:\n",
    "    json.dump({'apiEndpoint': api_endpoint}, outfile)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 2: Deploy frontend services"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# add NPM to the path so we can assemble the web frontend from our notebook code\n",
    "\n",
    "from os import environ\n",
    "\n",
    "npm_path = ':/home/ec2-user/anaconda3/envs/JupyterSystemEnv/bin'\n",
    "\n",
    "if npm_path not in environ['PATH']:\n",
    "    ADD_NPM_PATH = environ['PATH']\n",
    "    ADD_NPM_PATH = ADD_NPM_PATH + npm_path\n",
    "else:\n",
    "    ADD_NPM_PATH = environ['PATH']\n",
    "    \n",
    "%set_env PATH=$ADD_NPM_PATH"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%cd ./frontend/\n",
    "\n",
    "!npm install"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!npm run-script build"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "hosting_bucket = f\"s3://{outputs['s3BucketHostingBucketName']}\"\n",
    "\n",
    "!aws s3 sync ./build/ $hosting_bucket --acl public-read"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 3: Browse your frontend service, and upload an image"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('Click the URL below:\\n')\n",
    "if region.startswith('cn-'):\n",
    "    print(outputs['S3BucketSecureURL'].replace('.s3.amazonaws.com', '.s3.'+region+'.amazonaws.com.cn') + '/index.html')\n",
    "else:\n",
    "    print(outputs['S3BucketSecureURL'] + '/index.html')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You should see the following page:\n",
    "\n",
    "On the website, try pasting the following URL in the URL text field.\n",
    "\n",
    "`https://i4.ztat.net/large/VE/12/1C/14/8K/12/VE121C148-K12@10.jpg`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Extensions\n",
    "\n",
    "We have used pretrained Resnet50 model which is trained on Imagenet dataset. Now based on your use-case you can fine tune any pre-trained models, such as VGG, Inception, and MobileNet with your own dataset and host the model in Amazon SageMaker.\n",
    "\n",
    "You can also use Amazon SageMaker Batch transform job to have a bulk feaures extracted from your stored S3 images and then you can use AWS Glue to import that data into Elasticeearch domain.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Cleanup\n",
    "\n",
    "Make sure that you stop the notebook instance, delete the Amazon SageMaker endpoint and delete the Elasticsearch domain to prevent any additional charges."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Delete the endpoint\n",
    "# predictor.delete_endpoint()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Empty S3 Contents\n",
    "# training_bucket_resource = s3_resource.Bucket(bucket)\n",
    "# training_bucket_resource.objects.all().delete()\n",
    "\n",
    "# hosting_bucket_resource = s3_resource.Bucket(outputs['s3BucketHostingBucketName'])\n",
    "# hosting_bucket_resource.objects.all().delete()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "conda_mxnet_latest_p37",
   "language": "python",
   "name": "conda_mxnet_latest_p37"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
